package com.tools.common.io;

import com.tools.common.container.r.OnlyReadHashMap;
import com.tools.common.exception.IORuntimeException;
import com.tools.common.object.BasicEnum;
import com.tools.common.object.C;
import com.tools.common.object.Note;
import com.tools.common.object.ObjKit;
import com.tools.common.object.string.ValidLength;
import com.tools.common.thread.ThreadPoolSource;
import com.tools.common.thread.TimePair;
import com.tools.common.thread.Timeunit;
import com.tools.db.jdbc.DBSource;
import com.tools.middle.elasticsearch.ESSource;
import com.tools.middle.mq.MQSource;
import com.tools.middle.redis.RedisSource;
import com.tools.web.ali.AlipaySource;
import com.tools.web.email.MailSource;
import com.tools.web.filter.cors.CorsSource;
import com.tools.web.filter.gateway.GatewaySource;
import com.tools.web.interceptor.access.AccessSource;
import com.tools.web.interceptor.auth.AuthSource;
import com.tools.web.interceptor.limit.RequestLimitSource;
import com.tools.web.interceptor.quota.QuotaSource;
import com.tools.web.sms.NetSms;
import com.tools.web.work.obs.OBSSource;
import com.tools.web.wx.WxSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * 读取项目配置文件工具类
 * */
@Note("读取项目配置文件工具类")
public final class ProfileKit {

    @Note("配置项容器")
    private static Map<String, Object> CONFIG;

    @Note("是否扫描到 SpringBoot 核心配置文件 application")
    private static boolean HAS_APP;

    static {
        Logger logger = LoggerFactory.getLogger(ProfileKit.class);
        Map<String, Object> temp = new HashMap<>();
        private_readCustomConfig(logger, temp);
        logger.info("------------------------------------------------------------------");
        private_readApplication(logger, temp);
        CONFIG = new OnlyReadHashMap<>(temp);
    }


    @Note("重新刷新配置文件")
    public static void refreshAllConfig() {
        synchronized (ProfileKit.class) {
            Logger logger = LoggerFactory.getLogger(ProfileKit.class);
            Map<String, Object> temp = (CONFIG != null && !CONFIG.isEmpty())
                    ? new HashMap<>((int) ((CONFIG.size() / 0.75) + 1))
                    : new HashMap<>();
            private_readCustomConfig(logger, temp);
            logger.info("------------------------------------------------------------------");
            private_readApplication(logger, temp);
            CONFIG = new OnlyReadHashMap<>(temp);
        }
    }

    /* ************************************************************************************************************
     *
     *      公共方法
     *
     * ************************************************************************************************************
     * */

    @Note("获取配置项容器集合")
    public static Map<String, Object> getCONFIG() {
        return CONFIG;
    }

    @Note("是否扫描到 SpringBoot 核心配置文件 application")
    public static boolean hasApplication() {
        return HAS_APP;
    }


    @Note("移除配置文件集合的引用。【请在 SpringBoot 初始化完成后不再使用时再执行】")
    public static void clear() {
        if(CONFIG == null) return;
        synchronized (ProfileKit.class) {
            if(CONFIG != null) {
                CONFIG = null;
            }
        }
    }

    @Note("获取配置项的容器，注意该容器是只读容器，不能增删改配置项")
    public static Map<String, Object> copyProfileConfigMap() {
        return (CONFIG == null || CONFIG.isEmpty())
                ? new OnlyReadHashMap<>()
                : new OnlyReadHashMap<>(CONFIG);
    }

    @Note("根据配置项的名称获取配置项的内容")
    public static boolean contains(String name) {
        if(CONFIG == null || CONFIG.isEmpty()) return false;
        return CONFIG.containsKey(name);
    }

    @Note("根据配置项的名称获取配置项的内容")
    public static Object get(String name) {
        if(CONFIG == null || CONFIG.isEmpty()) return null;
        return CONFIG.get(name);
    }

    @Note("根据配置项的名称获取配置项的内容，并把内容强转为目标类型（若类型不兼容则报错）")
    public static <T> T get(String name, Class<T> castType) {
        if(CONFIG == null || CONFIG.isEmpty()) return null;
        return (T) CONFIG.get(name);
    }


    @Note("根据配置项的名称获取配置项的内容，并把内容尽可能地转化为目标类型")
    public static <T> T get(String name, C<T> bc) {
        if(CONFIG == null || CONFIG.isEmpty()) return null;
        Object o = CONFIG.get(name);
        return (T) ObjKit.toRealValue(o, bc);
    }


    @Note("根据配置项的名称获取配置项的内容，并把内容转为字符串返回")
    public static String getString(String name) {
        if(CONFIG == null || CONFIG.isEmpty()) return null;
        Object o = CONFIG.get(name);
        return (o == null) ? null : o.toString();
    }

    @Note("获取字符串类型的配置项参数返回，若参数为 null 则返回默认值")
    public static String getString(String name, String defaultValue) {
        String value = getString(name);
        return (value == null) ? defaultValue : value;
    }

    @Note("根据配置项的名称获取配置项的内容，并把内容转为 Integer 类型的整数返回")
    public static Integer getInteger(String name) {
        if(CONFIG == null || CONFIG.isEmpty()) return null;
        Object o = CONFIG.get(name);
        return BasicEnum.intValue(o);
    }

    @Note("获取 Integer 类型的配置项参数返回，若参数为 null 则返回默认值")
    public static Integer getInteger(String name, int defaultValue) {
        Integer value = getInteger(name);
        return (value == null) ? defaultValue : value;
    }

    @Note("根据配置项的名称获取配置项的内容，并把内容转为 Integer 类型的整数返回")
    public static Long getLong(String name) {
        if(CONFIG == null || CONFIG.isEmpty()) return null;
        Object o = CONFIG.get(name);
        return BasicEnum.longValue(o);
    }

    @Note("获取 Long 类型的配置项参数返回，若参数为 null 则返回默认值")
    public static Long getLong(String name, long defaultValue) {
        Long value = getLong(name);
        return (value == null) ? defaultValue : value;
    }

    @Note("根据配置项的名称获取配置项的内容，并把内容转为 Integer 类型的整数返回")
    public static Double getDouble(String name) {
        if(CONFIG == null || CONFIG.isEmpty()) return null;
        Object o = CONFIG.get(name);
        return BasicEnum.doubleValue(o);
    }

    @Note("获取 Double 类型的配置项参数返回，若参数为 null 则返回默认值")
    public static Double getDouble(String name, double defaultValue) {
        Double value = getDouble(name);
        return (value == null) ? defaultValue : value;
    }


    @Note("根据配置项的名称获取配置项的内容，并把内容转为 Integer 类型的整数返回")
    public static Float getFloat(String name) {
        if(CONFIG == null || CONFIG.isEmpty()) return null;
        Object o = CONFIG.get(name);
        return BasicEnum.floatValue(o);
    }

    @Note("获取 Float 类型的配置项参数返回，若参数为 null 则返回默认值")
    public static Float getFloat(String name, float defaultValue) {
        Float value = getFloat(name);
        return (value == null) ? defaultValue : value;
    }

    @Note("根据配置项的名称获取配置项的内容，并把内容转为 Integer 类型的整数返回")
    public static Boolean getBoolean(String name) {
        if(CONFIG == null || CONFIG.isEmpty()) return null;
        Object o = CONFIG.get(name);
        return BasicEnum.booleanValue(o);
    }

    @Note("获取 Float 类型的配置项参数返回，若参数为 null 则返回默认值")
    public static Boolean getBoolean(String name, boolean defaultValue) {
        Boolean value = getBoolean(name);
        return (value == null) ? defaultValue : value;
    }

    @Note("根据配置项的名称获取配置项的内容，并把内容转为时间单位枚举实例返回")
    public static Timeunit getTimeunit(String name) {
        if(CONFIG == null || CONFIG.isEmpty()) return null;
        String unit = getString(name);
        return Timeunit.getEnumSafeByUnit(unit);
    }

    @Note("获取时间单位枚举实例返回，若实例为 null 则返回默认值")
    public static Timeunit getTimeunit(String name, Timeunit defaultValue) {
        Timeunit timeunit = getTimeunit(name);
        return (timeunit == null) ? defaultValue : timeunit;
    }

    @Note("解析字符串，获取时间配置项和其单位的，并封装为 TimePair 实例返回")
    public static TimePair getTimePair(String name) {
        if(CONFIG == null || CONFIG.isEmpty()) return null;
        String s = (String) CONFIG.get(name);
        return TimePair.buildSafe(s);
    }

    @Note("获取时间配置项和其单位的，并封装为 TimePair 实例返回")
    public static TimePair getTimePair(String timeName, String timeunitName) {
        if(CONFIG == null || CONFIG.isEmpty()) return null;
        Long time = getLong(timeName);
        if(time == null) return null;
        Timeunit timeunit = getTimeunit(timeunitName);
        if(timeunit == null) return null;
        return new TimePair(time, timeunit);
    }

    @Note("获取长度验证器")
    public static ValidLength getValidLength(String minName, String maxName) {
        if(CONFIG == null || CONFIG.isEmpty()) return null;
        Integer min = getInteger(minName);
        Integer max = getInteger(maxName);
        if(min == null || max == null) return null;
        return new ValidLength(min, max);
    }

    @Note("获取配置文件下配置的文件的绝对路径")
    public static String getResourcePath(String name) {
        if(CONFIG == null || CONFIG.isEmpty()) return "";
        String path = (String) CONFIG.get(name);
        return FileKit.getResourceFilePath(path);
    }

    @Note("获取配置文件下配置的文件的字节流")
    public static InputStream getResourceAsStream(String name) {
        if(CONFIG == null || CONFIG.isEmpty()) return null;
        String path = (String) CONFIG.get(name);
        return FileKit.getResourceInputStream(path);
    }

    @Note("获取 Servlet 的上下文路径")
    public static String getServerContextPath() {
        if(CONFIG == null || CONFIG.isEmpty()) return "";
        String path = (String) CONFIG.get("server.servlet.context-path");
        if(path != null) return path;
        return (String) CONFIG.get("server-servlet-context-path");
    }


    @Note("获取容器中配置项的个数")
    public static int size() {
        if(CONFIG == null || CONFIG.isEmpty()) return 0;
        return CONFIG.size();
    }


    @Note("把容器转为一个新的 Properties 对象返回")
    public static Properties toProperties() {
        return ObjKit.toProperties(CONFIG);
    }

    /* ************************************************************************************************************
     *
     *      获取配置项，并将其转为对应配置实例的方法
     *
     * ************************************************************************************************************
     * */

    @Note("把配置文件中关于自定义的线程池的所有配置项扫描出来，然后封装成一个 ThreadPoolSource 包装类对象返回")
    public static ThreadPoolSource getThreadPoolSource() {
        return ThreadPoolSource.from(CONFIG);
    }

    @Note("把配置文件中关于数据库的配置项扫描出来，然后封装成一个 DBSource 包装类对象返回")
    public static DBSource getDataBaseSource() {
        return DBSource.from(CONFIG, HAS_APP);
    }

    @Note("把配置文件中关于自定义的邮箱的所有配置项扫描出来，然后封装成一个 MailSource 包装类对象返回")
    public static MailSource getMailSource() {
        return MailSource.from(CONFIG);
    }


    @Note("把配置文件中关于自定义的 Redis 的所有配置项扫描出来，然后封装成一个 RedisSource 包装类对象返回")
    public static RedisSource getRedisSource() {
        return RedisSource.from(CONFIG);
    }

    @Note("把配置文件中关于 OBS 对象存储的配置项扫描出来，然后封装成一个 ObsSource 包装类对象返回")
    public static OBSSource getObsSource() {
        return OBSSource.from(CONFIG);
    }


    @Note("把配置文件中关支付宝登录的配置项扫描出来，然后封装成一个 AlipaySource 包装类对象返回")
    public static AlipaySource getAlipaySource() {
        return AlipaySource.from(CONFIG);
    }


    @Note("把配置文件中关于自定义的 ElasticSearch 的所有配置项扫描出来，然后封装成一个 ESSource 包装类对象返回")
    public static ESSource getESSource() {
        return ESSource.from(CONFIG);
    }


    @Note("获取自定义过滤器配置项包装对象")
    public static CorsSource getCorsSource() {
        return CorsSource.from(CONFIG);
    }


    @Note("获取自定义网关配置项包装对象")
    public static GatewaySource getGatewaySource() {
        return GatewaySource.from(CONFIG);
    }


    @Note("获取限流拦截器的配置参数包装对象")
    public static RequestLimitSource getRequestLimitSource() {
        return RequestLimitSource.from(CONFIG);
    }


    @Note("获取用户认证拦截器的配置参数保证对象")
    public static AuthSource getAuthSource() {
        return AuthSource.from(CONFIG);
    }


    @Note("权限验证拦截器所需的参数配置项的包装类实例")
    public static AccessSource getAccessSource() {
        return AccessSource.from(CONFIG);
    }

    @Note("获取配额限制拦截器所需的参数配置项的包装类实例")
    public static QuotaSource getQuotaSource() {
        return QuotaSource.from(CONFIG);
    }


    @Note("获取网建短信通服务实例对象")
    public static NetSms getNetSms() {
        return NetSms.from(CONFIG);
    }

    @Note("获取微信登录所需配置项的包装实例对象")
    public static WxSource getWxSource() {
        return WxSource.from(CONFIG);
    }

    @Note("获取消息队列所需配置项的包装类对象")
    public static MQSource mqSource() {
        return MQSource.from(CONFIG);
    }


    /* ************************************************************************************************************
     *
     *      私有逻辑
     *
     * ************************************************************************************************************
     * */


    @Note("读取 custom-config.properties 配置文件")
    private static void private_readCustomConfig(Logger logger, Map<String, Object> temp) {
        String customConfigPath = FileKit.getResourceFilePath(FileKit.CLI_DIR_NAME + "/custom-config.properties");
        File file = new File(customConfigPath);
        if(file.exists()) {
            private_customConfigDataPutInMap(file, logger, temp);
            return;
        }
        customConfigPath = FileKit.getResourceFilePath("custom-config.properties");
        file = new File(customConfigPath);
        private_customConfigDataPutInMap(file, logger, temp);
    }

    @Note("读取 custom-config.properties 自定义配置文件及其配置的子文件的数据项，放入到 temp Map 里")
    private static void private_customConfigDataPutInMap(File file, Logger logger, Map<String, Object> temp) {
        String filename = file.getName();
        String parent = file.getParent();
        logger.info("正在扫描 {} 文件，目录路径 {} ...", filename, parent);
        Properties properties = private_readToProperties(file);
        if(properties == null) {
            logger.error("目录 {} 下的 {} 文件不存在!", parent, filename);
            return;
        }
        String additional = properties.getProperty("additional-filename");
        if(additional != null && !additional.isEmpty() && "custom-config.properties".equals(filename)) {
            String[] a = additional.split(",");
            logger.info("检测到 {} 的子配置文件: {}", filename, Arrays.toString(a));
            String suffix = FileKit.getFileSuffix(filename);
            String classPath = FileKit.getClassPath();
            for (String fn : a) {
                File f = new File(classPath, fn + suffix);
                private_customConfigDataPutInMap(f, logger, temp);
            }
            private_propertiesFillInTemp(logger, properties, temp, filename);
            return;
        }
        private_propertiesFillInTemp(logger, properties, temp, filename);
    }



    @Note("读取 SpringBoot 核心配置文件的所有配置项")
    private static void private_readApplication(Logger logger, Map<String, Object> temp) {
        logger.info("正在检测是否存在 SpringBoot 核心配置文件: [application.properties, application.yml]...");
        String applicationProPath = FileKit.getResourceFilePath("application.properties");
        boolean proIsExists = private_applicationProDataPutInMap(new File(applicationProPath), logger, temp);
        if(proIsExists) {
            HAS_APP = true;
            return;
        }
        String applicationYmlPath = FileKit.getResourceFilePath("application.properties");
        boolean ymlIsExists = private_applicationYmlDataPutInMap(new File(applicationYmlPath), logger, temp);
        if(ymlIsExists) {
            HAS_APP = true;
            return;
        }
        HAS_APP = false;
        logger.error("没有检索到 SpringBoot 核心配置文件: [application.properties, application.yml] !!!");
    }


    @Note("读取 SpringBoot 核心配置文件 【application.properties】 的所有配置项")
    private static boolean private_applicationProDataPutInMap(File file, Logger logger, Map<String, Object> temp) {
        Properties properties = private_readToProperties(file);
        if(properties == null) return false;
        String filename = file.getName();
        if("application.properties".equals(filename)) {
            logger.info("检测到 SpringBoot 核心配置文件: 【application.properties】，正在加载其中的配置项...");
            String actives = properties.getProperty("spring.profiles.active");
            if(actives != null && !actives.isEmpty()) {
                String[] a = actives.split(",");
                logger.info("检测到 application.properties 的子配置文件: {}", Arrays.toString(a));
                for (String fn : a) {
                    File aFile = FileKit.getResourceFile("application-" + fn + ".properties");
                    private_applicationProDataPutInMap(aFile, logger, temp);
                }
            }
        } else {
            logger.info("检测到 SpringBoot 核心子配置文件: 【{}】，正在加载其中的配置项...", filename);
        }
        for (Object o : properties.entrySet()) {
            Map.Entry e = (Map.Entry) o;
            temp.put((String) e.getKey(), ObjKit.stringToRealValue((String) e.getValue(), false));
        }
        logger.info("{} 文件扫描完成，共加载 {} 项!", filename, properties.size());
        return true;
    }


    @Note("读取 SpringBoot 核心配置文件 【application.yml】 的所有配置项")
    private static boolean private_applicationYmlDataPutInMap(File file, Logger logger, Map<String, Object> temp) {
        if(!file.exists()) return false;
        Map<String, Object> ymlMap = FileKit.readYml(file);
        String filename = file.getName();
        if("application.yml".equals(filename)) {
            logger.info("检测到 SpringBoot 核心配置文件: 【application.yml】，正在加载其中的配置项...");
            String actives = (String) ymlMap.get("spring.profiles.active");
            if(actives != null && !actives.isEmpty()) {
                String[] a = actives.split(",");
                logger.info("检测到 application.yml 的子配置文件: {}", Arrays.toString(a));
                for (String fn : a) {
                    File aFile = FileKit.getResourceFile("application-" + fn + ".yml");
                    private_applicationProDataPutInMap(aFile, logger, temp);
                }
            }
        } else {
            logger.info("检测到 SpringBoot 核心子配置文件: 【{}】，正在加载其中的配置项...", filename);
        }
        temp.putAll(ymlMap);
        logger.info("{} 文件扫描完成，共加载 {} 项!", filename, ymlMap.size());
        return true;
    }



    @Note("读取 .properties 文件并返回一个 Properties 对象")
    private static Properties private_readToProperties(File file) {
        if(!file.exists()) {
            return null;
        }
        FileReader reader = null;
        try {
            reader = new FileReader(file);
            Properties properties = new Properties();
            properties.load(reader);
            return properties;
        } catch (IOException e) {
            throw new IORuntimeException(e);
        } finally {
            IOKit.close(reader);
        }
    }


    @Note("将读取到的 Properties 文件全部填充到临时 Map 里")
    private static void private_propertiesFillInTemp(Logger logger, Properties properties,
                                                     Map<String, Object> temp, String filename) {
        for (Object o : properties.entrySet()) {
            Map.Entry e = (Map.Entry) o;
            String key = ((String) e.getKey()).trim();
            String value = ((String) e.getValue()).trim();
            temp.put(key, ObjKit.stringToRealValue(value, false));
        }
        logger.info("{} 文件扫描完成，共加载 {} 项!", filename, properties.size());
    }
}
